﻿// 2025/10/14: 首个版本

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.IO;
using System.Text;
using Avalonia;
using Avalonia.Threading;
using Avalonia.Controls;
using Avalonia.Input;
using ASEva;
using ASEva.UIAvalonia;
using ASEva.Utility;

namespace CommonSource
{
    partial class PluginConsole : DialogPanel, ConsoleIO
    {
        public PluginConsole() // For designer
        {
            InitializeComponent();
        }

        public PluginConsole(ConsoleClass console, String title, String iconResourceName = null)
        {
            InitializeComponent();
            SetResizableMode(600, 400, 800, 500);
            Icon = ResourceLoader.Load(iconResourceName)?.ToAvaloniaWindowIcon();
            Title = title;

            debuggerPanel.ModuleID = "plugin-console.system";
            delayFocus(entry);

            Loaded += delegate
            {
                if (thread != null) return;
                thread = new Thread(() =>
                {
                    try
                    {
                        console.RunConsole(this);
                        console.RunConsole(this, false); // 兼容旧版本
                    }
                    catch (Exception ex)
                    {
                        AgencyLocal.Print("RunConsole failed: " + ex.Message + "\n" + ex.StackTrace);
                    }
                    if (!closing) runOver = true;
                });
                thread.Start();
            };

            entry.KeyDown += (o, e) =>
            {
                if (e.Key == Key.Enter)
                {
                    lock (lck)
                    {
                        response = entry.Text == null ? "" : entry.Text.Clone() as String;
                    }
                    entry.Text = "";
                    e.Handled = true;
                }
            };

            timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(18);
            timer.Tick += async delegate
            {
                lock (lck)
                {
                    foreach (var msg in printMsgs)
                    {
                        debuggerPanel.InputMessage(msg, "");
                    }
                    printMsgs.Clear();
                }
                debuggerPanel.QueueRender();

                SelectFolderContext localFolderCtx = null;
                lock (lck)
                {
                    localFolderCtx = folderCtx;
                }
                if (localFolderCtx != null && localFolderCtx.Status == 0)
                {
                    localFolderCtx.Status = 1;
                    var dirPath = await App.ShowSelectFolderDialog(this, localFolderCtx.Message);
                    if (dirPath != null) localFolderCtx.Result = dirPath;
                    localFolderCtx.Status = 2;
                }

                SelectOpenFileContext localOpenFileCtx = null;
                lock (lck)
                {
                    localOpenFileCtx = openFileCtx;
                }
                if (localOpenFileCtx != null && localOpenFileCtx.Status == 0)
                {
                    var filters = new Dictionary<string, string[]>();
                    if (localOpenFileCtx.Extensions != null && localOpenFileCtx.Extensions.Length > 0)
                    {
                        foreach (var ext in localOpenFileCtx.Extensions)
                        {
                            if (!String.IsNullOrEmpty(ext) && ext.StartsWith('.'))
                            {
                                filters[ext + (AgencyLocal.GetAppLanguage() == Language.Chinese ? "文件" : " file")] = [ext];
                            }
                        }
                    }
                    localOpenFileCtx.Status = 1;
                    var filePaths = await App.ShowOpenFileDialog(this, localOpenFileCtx.Message, filters: filters);
                    if (filePaths != null && filePaths.Length == 1) localOpenFileCtx.Result = filePaths[0];
                    localOpenFileCtx.Status = 2;
                }

                SelectSaveFileContext localSaveFileCtx = null;
                lock (lck)
                {
                    localSaveFileCtx = saveFileCtx;
                }
                if (localSaveFileCtx != null && localSaveFileCtx.Status == 0)
                {
                    String filterTitle = null, filterSuffix = null;
                    if (!String.IsNullOrEmpty(localSaveFileCtx.Extension) && localSaveFileCtx.Extension.StartsWith('.'))
                    {
                        filterTitle = localSaveFileCtx.Extension + (AgencyLocal.GetAppLanguage() == Language.Chinese ? "文件" : " file");
                        filterSuffix = localSaveFileCtx.Extension;
                    }
                    localSaveFileCtx.Status = 1;
                    var filePath = await App.ShowSaveFileDialog(this, localSaveFileCtx.Message, filterTitle: filterTitle, filterSuffix: filterSuffix);
                    if (filePath != null) localSaveFileCtx.Result = filePath;
                    localSaveFileCtx.Status = 2;
                }

                if (runOver)
                {
                    runOver = false;
                    Close();
                }
            };
            timer.Start();
        }

        public override void OnClosing()
        {
            closing = true;
            thread.Join();
            timer.Stop();
        }

        public void Print(string message)
        {
            addMessage(safe(message));
        }

        public bool Confirm(string message, ref bool result)
        {
            addMessage(safe(message));

            var input = waitInput();
            if (input == null) return false;

            var lower = input.ToLower();
            if (lower == "o" || lower == "1" || lower == "y" || lower == "yes") result = true;
            if (lower == "x" || lower == "0" || lower == "n" || lower == "no") result = false;
            return true;
        }

        public bool InputNumber(string message, ref double result)
        {
            addMessage(safe(message) + " (default=" + result + ")");

            var input = waitInput();
            if (input == null) return false;

            double parsed = 0;
            if (Double.TryParse(input, out parsed)) result = parsed;
            return true;
        }

        public bool InputString(string message, ref string result)
        {
            addMessage(safe(message) + (String.IsNullOrEmpty(result) ? "" : (" (default=" + result + ")")));

            var input = waitInput();
            if (input == null) return false;

            if (input.Length > 0) result = input;
            return true;
        }

        public bool SingleSelect(string message, string[] options, ref int result)
        {
            if (options == null || options.Length == 0)
            {
                result = -1;
                return true;
            }

            var rows = new List<String>();
            rows.Add(message);
            for (int i = 0; i < options.Length; i++)
            {
                rows.Add((i + 1) + ". " + (result == i ? "(+) " : "( ) ") + safe(options[i]));
            }
            addMessage(String.Join('\n', rows));

            var input = waitInput();
            if (input == null) return false;

            int selectedIndex = 0;
            if (input.Length == 0 || !Int32.TryParse(input, out selectedIndex) || selectedIndex <= 0 || selectedIndex > options.Length) return true;

            result = selectedIndex - 1;
            return true;
        }

        public bool MultiSelect(string message, string[] options, ref int[] result)
        {
            if (options == null || options.Length == 0)
            {
                result = new int[0];
                return true;
            }

            var selected = new List<int>();
            if (result != null) selected.AddRange(result);

            var rows = new List<String>();
            rows.Add(safe(message));
            for (int i = 0; i < options.Length; i++)
            {
                rows.Add((i + 1) + ". " + (selected.Contains(i) ? "[+] " : "[ ] ") + safe(options[i]));
            }
            addMessage(String.Join('\n', rows));

            var input = waitInput();
            if (input == null) return false;
            if (input.Length == 0) return true;

            var flags = new Dictionary<int, bool>();
            foreach (var comp in input.Split(','))
            {
                var trimmed = comp.Trim();
                if (trimmed.Length == 0) continue;

                int index = 0;
                if (!Int32.TryParse(trimmed, out index)) continue;
                if (index <= 0 || index > options.Length) continue;
                flags[index - 1] = true;
            }

            var indices = flags.Keys.ToList();
            indices.Sort();
            result = indices.ToArray();
            return true;
        }

        public bool SelectFolder(string message, out string result)
        {
            result = null;

            lock (lck)
            {
                folderCtx = new SelectFolderContext
                {
                    Message = safe(message)
                };
            }

            var selected = waitSelect(folderCtx);
            lock (lck)
            {
                folderCtx = null;
            }
            if (selected == null) return false;

            if (selected.Length > 0) result = selected;            
            return true;
        }

        public bool SelectOpenFile(string message, string[] extensions, out string result)
        {
            result = null;

            lock (lck)
            {
                openFileCtx = new SelectOpenFileContext
                {
                    Message = safe(message),
                    Extensions = extensions
                };
            }

            var selected = waitSelect(openFileCtx);
            lock (lck)
            {
                openFileCtx = null;
            }
            if (selected == null) return false;

            if (selected.Length > 0) result = selected;
            return true;
        }

        public bool SelectSaveFile(string message, string extension, out string result)
        {
            result = null;

            lock (lck)
            {
                saveFileCtx = new SelectSaveFileContext
                {
                    Message = safe(message),
                    Extension = extension
                };
            }

            var selected = waitSelect(saveFileCtx);
            lock (lck)
            {
                saveFileCtx = null;
            }
            if (selected == null) return false;

            if (selected.Length > 0) result = selected;
            return true;
        }

        public bool LoadFileData(string message, string[] extensions, out byte[] data, out ConsoleFileResult result)
        {
            data = null;
            result = new ConsoleFileResult();

            String filePath = null;
            if (!SelectOpenFile(message, extensions, out filePath)) return false;
            if (String.IsNullOrEmpty(filePath)) return true;

            using (var stream = File.OpenRead(filePath))
            {
                data = new byte[stream.Length];
                stream.Read(data);
            }
            result.OK = true;
            result.FilePath = filePath;
            result.IsClientFile = false;
            return true;
        }

        public bool SaveFileData(string message, string extension, byte[] data, out ConsoleFileResult result)
        {
            result = new ConsoleFileResult();

            if (data == null) return true;

            String filePath = null;
            if (!SelectSaveFile(message, extension, out filePath)) return false;
            if (String.IsNullOrEmpty(filePath)) return true;

            using (var stream = File.Create(filePath))
            {
                if (data.Length > 0) stream.Write(data);
            }
            result.OK = true;
            result.FilePath = filePath;
            result.IsClientFile = false;
            return true;
        }

        private String safe(String message)
        {
            return String.IsNullOrEmpty(message) ? "na" : message;
        }

        private void addMessage(String message)
        {
            lock (printMsgs)
            {
                printMsgs.Add("---\n" + message);
            }
        }

        private String waitInput()
        {
            lock (lck)
            {
                response = null;
            }
            while (true)
            {
                Thread.Sleep(12);
                if (closing) return null;
                lock (lck)
                {
                    if (response != null) return response;
                }
            }
        }

        private String waitSelect(SelectContext ctx)
        {
            while (true)
            {
                Thread.Sleep(12);
                if (closing) return null;
                lock (lck)
                {
                    if (ctx.Status == 2) return ctx.Result == null ? "" : ctx.Result;
                }
            }
        }

        private async void delayFocus(TextBox textBox)
        {
            await Task.Delay(300);
            textBox.Focus();
        }

        private class TestConsoleClass : ConsoleClass
        {
            public override void RunConsole(ConsoleIO io)
            {
                io.Print("Test Print");

                bool confirmResult = false;
                if (!io.Confirm("Test Confirm", ref confirmResult)) return;
                AgencyLocal.Print("Confirm result: " + confirmResult);

                double number = 123;
                if (!io.InputNumber("Test InputNumber", ref number)) return;
                AgencyLocal.Print("InputNumber result: " + number);

                String text = "Hello world";
                if (!io.InputString("Test InputString", ref text)) return;
                AgencyLocal.Print("InputString result: " + text);

                int selectedIndex = 1;
                var options = new String[] { "Tom", "Mary", "Bob" };
                if (!io.SingleSelect("Test SingleSelect", options, ref selectedIndex)) return;
                AgencyLocal.Print("SingleSelect result: " + options[selectedIndex]);

                var selectedIndices = new int[] { 0, 2 };
                if (!io.MultiSelect("Test MultiSelect", options, ref selectedIndices)) return;
                AgencyLocal.Print("MultiSelect result: " + String.Join(", ", Array.ConvertAll<int, String>(selectedIndices, i => options[i])));

                String openFile = null;
                if (!io.SelectOpenFile("Test SelectOpenFile", new String[] { ".txt", ".xml" }, out openFile)) return;
                AgencyLocal.Print("SelectOpenFile result: " + openFile);

                String saveFile = null;
                if (!io.SelectSaveFile("Test SelectSaveFile", ".txt", out saveFile)) return;
                AgencyLocal.Print("SelectSaveFile result: " + saveFile);

                String folder = null;
                if (!io.SelectFolder("Test SelectFolder", out folder)) return;
                AgencyLocal.Print("SelectFolder Result: " + folder);

                byte[] loadedFileData = null;
                ConsoleFileResult result = null;
                if (!io.LoadFileData("Test LoadFileData", new String[] { ".txt" }, out loadedFileData, out result)) return;
                if (result.OK)
                {
                    using (var reader = new StreamReader(new MemoryStream(loadedFileData)))
                    {
                        AgencyLocal.Print("LoadFileData Result: " + reader.ReadToEnd());
                    }
                    AgencyLocal.Print("LoadFileData Path: " + result.FilePath + " (" + (result.IsClientFile ? "Client" : "Local") + ")");
                }

                byte[] fileDataToSave = null;
                using (var stream = new MemoryStream())
                {
                    using (var writer = new StreamWriter(stream, Encoding.UTF8))
                    {
                        writer.WriteLine("Hello World! 天气不错！");
                        writer.Flush();
                        stream.Position = 0;
                        fileDataToSave = new byte[stream.Length];
                        stream.Read(fileDataToSave);
                    }
                }
                if (!io.SaveFileData("Test SaveFileData", ".txt", fileDataToSave, out result)) return;
                if (result.OK)
                {
                    AgencyLocal.Print("SaveFileData Path: " + result.FilePath + " (" + (result.IsClientFile ? "Client" : "Local") + ")");
                }

                text = null;
                io.InputString("Enter to exit", ref text);
            }
        }

        public static async Task RunTestConsole()
        {
            await App.RunDialog(new PluginConsole(new TestConsoleClass(), "Test Console"));
        }

        private class SelectContext
        {
            public int Status { get; set; } // 0: init, 1: handling, 2: handled
            public String Result { get; set; }
        }

        private class SelectOpenFileContext : SelectContext
        {
            public String Message { get; set; }
            public String[] Extensions { get; set; }
        }

        private class SelectSaveFileContext : SelectContext
        {
            public String Message { get; set; }
            public String Extension { get; set; }
        }

        private class SelectFolderContext : SelectContext
        {
            public String Message { get; set; }
        }
        
        private DispatcherTimer timer;
        private List<String> printMsgs = new List<string>();
        private String response;
        private SelectOpenFileContext openFileCtx;
        private SelectSaveFileContext saveFileCtx;
        private SelectFolderContext folderCtx;
        private object lck = new object();
        private bool closing = false;
        private bool runOver = false;
        private Thread thread;
    }
}